/*
在JavaScript中，this指当前执行上下文(current execution context)，它是一个对象。
JavaScript 运行时(runtime)确定current execution context的方式比Java复杂得多。

在JavaScript中，有一个执行上下文堆栈(execution context stack)，
由活动的执行上下文(active execution contexts)逻辑形成。当控制权
从一个可执行代码转移到另一个可执行代码时，控制权进入新可执行代码的
execution context，该execution context将成为current execution context，
或称为running execution context。

堆栈的底部是global context，一切都从这里开始，就像Java中的main方法一样。
current execution context始终位于堆栈的顶部。

什么是可执行代码? JavaScript中有三种类型:
1 全局代码，即从JavaScript程序启动的位置运行的代码。
  在浏览器中，它是窗口所在的位置。当打开浏览器控制台并输入
    var user = new User()时，正在编写全局代码。
2 Eval代码，它是作为内置 eval()函数的参数传入的字符串值
  (除非真的知道自己在做什么，否则不要使用 eval() 函数)。
3 函数代码，即解析为函数主体的代码。但是，这并不意味着在函数中编写的
  所有代码都是函数代码。
*/

/*
    * 由于execution context实际上是一个对象，因此可以使用它的 .constructor.name 来查看上下文是什么
    * 当JavaScript引擎看到function declaration时，它将创建一个函数对象，该对象在声明函数的范围内可见
    * 
    * 在JavaScript中，scope和exection context是两个不同的概念。
    *   scope是关于可访问性的，而exection context是关于运行可执行代码的所有权。
    *  例如 
    *   speak()方法和ask()函数在同一个scope，但它们具有不同的context。
    *   当执行ask()函数时，具有global context(在浏览器中是window, 在node中是global)，name属性解析为值Unknown，
    *     该值在global scope内声明。
    *   当执行 speak()方法时，它具有User context。从输出中可以看到，它对name属性的访问解析为Ted。

    * 

*/
//User是function declaration, global scope范围可见
function User(name) {
    //调用User函数 execution context变化
    //所以下一句 输出 I'm in "User" context
    console.log('1. I\'m in "' + this.constructor.name + '" context.');
    this.name = name;
    //function expression, 
    this.speak = function() {
        console.log('2. ' + this.name + ' is speaking form "' + this.constructor.name +  '" context.');

        //function expression, 在speak function scope 可见
        var drink = function() {
            console.log('3. Drinking in "' + this.constructor.name + '"');
        }
        drink();
    };

    //ask是function declaration, 在User function scope范围内可见
    function ask() {
        console.log('4. Asking from "' + this.constructor.name + ' "context');
        console.log('5. Who am I? "' + this.name + '"');
    }

    //因为ask()函数不是对象的属性，也不是由Function.call()方法调用的，
    //  所以global成为execution contxt。所以会看到
    //   Asking from "Object "context
    //   Who am I? "Unknown" 
    ask();

    // Asking from "User "context
    // Who am I? "Ted"
    ask.call(this);

    // Asking from "User "context
    // Who am I? "Ted"
    ask.bind(this)();
}

//var name = 'Unknown';    //browser环境
global.name =  'Unknown';  //node环境
var user = new User('Ted');

//speak())方法，User对象成为execution context。
// 将看到Ted is speaking form "User" context.。
//在输出中。当引擎执行drink()函数时，它会解析回global为execution context。
// 将看到Drinking in "Object"。
user.speak();


// Jack is speaking form "Object" context.
// Drinking in "Object"
user.speak.apply({name: 'Jack'}) 


//call(), apply(), bind()的作用
/*
每个函数都是由Function对象创建的对象。以下代码后，可以看到speak()函数
继承了函数原型的属性，包括call()、apply()和bind()方法
*/
console.log(Function.prototype.isPrototypeOf(user.speak)); // true 
console.log(user.speak.hasOwnProperty('apply'));           // false 
console.log(user.speak.__proto__.hasOwnProperty('apply')); // true

/*
call()方法和apply()方法类似。这两种方法之间的区别在于call()方法接受参数列表，
而apply()方法接受参数数组。
这两种方法都将第一个参数作为函数代码的execution context。例如，在
  user.speak.apply（{name： 'Jack'}）中，
{name： 'Jack'} 对象将是用户的speak()方法的执行上下文。
可以将call()和apply()方法视为切换执行上下文的一种方式。

bind()方法的行为与其他两个方法不同。bind()方法的作用是创建一个新函数，该
函数将绑定到作为新函数的执行上下文传入的第一个参数。新函数永远不会更改其执行上下文，
即使使用call()或apply()来切换执行上下文也是如此。因此，
  ask.bind(this)()
所做的是创建一个函数，然后立即执行它。除了立即执行它之外，还可以将新函数分配给变量
或作为对象的方法。

总结一下，有四种方法可以调用函数：
构造函数调用：new User()
直接函数调用：ask()
调用方法：user.speak()
切换上下文调用：ask.call(this)或 ask.apply(this)

当谈论构造函数调用时，除了那些被其他三种调用类型的函数包装的实例之外，函数体内存在它是指构造函数创建的对象。
当谈论直接函数调用时，除了那些被其他三种调用类型的函数包装的实例之外，函数体内的存在是指全局上下文。
当谈论方法调用时，除了那些被其他三种调用类型的函数包装的实例之外，函数体内存在此方法是指该方法所属的对象。
当谈论切换上下文调用时，除了那些被其他三种类型的调用的函数包装的实例之外，函数体内存在它是指作为call()方法的第一个参数传入的对象。
*/


//***这个程序在浏览器中更明显